package hasthi.wsdm.capabilities;

import static hasthi.common.constants.HasthiConstants.UNSPECIFIED_EPR;
import static hasthi.common.constants.WSDMConstants.MANAGMENT_EVNET_SINK_PARAM;
import hasthi.common.BaseContext;
import hasthi.common.ErrorManager;
import hasthi.common.HasthiException;
import hasthi.common.HasthiOptions;
import hasthi.common.LogFile;
import hasthi.common.Utils;
import hasthi.common.XmlBeanUtils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.WSDMConstants;
import hasthi.config.PropertyType;
import hasthi.container.SimpleMessageContext;
import hasthi.manager.ManageMeRequestDocument;
import hasthi.manager.ManageMeRequestDocument.ManageMeRequest;
import hasthi.manager.ManageMeRequestDocument.ManageMeRequest.ResourceDetails;
import hasthi.metamodel.ChangeBuffer;
import hasthi.metamodel.ChangeEvent;
import hasthi.tools.FailureDetectionTools;
import hasthi.tools.analyze.DataCollector;
import hasthi.wsdm.Capability;
import hasthi.wsdm.CapabilityDescription;
import hasthi.wsdm.ManagableResource;
import hasthi.wsdm.ManagementAgentContext;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.XmlCalendar;
import org.apache.xmlbeans.XmlObject;

import wsdm.muws.HeartbeatReportDocument;
import wsdm.muws.ManagementEventDocument;
import wsdm.muws.ManagementEventType;
import wsdm.muws.SituationDocument;
import wsdm.muws.SituationType;
import wsmg.WseClientAPI;
import edu.indiana.extreme.wsdm.PiggybackParameterListDocument;

/**
 * When this capability is enabled, it will collect following information and
 * send them to designated managment endpoint once every heartbeatInterval.
 * <ol>
 * <li>Matrics</li>
 * <li>State changes happend on last heartbeatIntervals</li>
 * </ol>
 * 
 * @author Srinath Perera(hperera@cs.indiana.edu)
 */

public class HeartBeatCapability extends AbstractCapability {
    
    private static QName CURRENT_TIME = new QName(WSDMConstants.MUWS2_NAMESPACE, "CurrentTime");

    private Capability matrics1Capability;

    private ChangeBuffer changeBuffer;
    private WseClientAPI wseClient;

    private ManagementEventDocument eventDocumentTemplate;
    private ManageMeRequestDocument manageMeRequestTemplate;
    
    private DataCollector collector; 
    
    private int timeWithoutAManager = 0;
    private ScheduledFuture taskhandle;
    private boolean logHistoy = false;
    private LogFile logfile;
    
    private boolean running = true;
    
    
    public HeartBeatCapability() {
        super();
    }
 
    public void setProperty(QName name, Object value) throws HasthiException {
        throw new HasthiException("Unsupported property "+name + " set in capability "+ this,FaultCode.InvaliedLocalArgumnet);
    }
   
    public Object getProperty(QName name) throws HasthiException {
        if (CURRENT_TIME.equals(name)) {
            XmlCalendar calendar = new XmlCalendar(new Date());
            return calendar.toString();
        }
        throw new HasthiException("Unsupported property "+name + " requested from capability "+ this,FaultCode.InvaliedLocalArgumnet);
    }

    @Override
    public void init(ManagementAgentContext context, ManagableResource ownedResource,
            CapabilityDescription description) throws HasthiException {
        super.init(context, ownedResource, description);
        wseClient = new WseClientAPI();
        //This is the template we use for sending Heartbeat event
        //XmlCalendar currentTime = new XmlCalendar(new Date());
        Calendar currentTime = new GregorianCalendar();
        eventDocumentTemplate = createBaseEvent(currentTime);
        ManagementEventType eventType = eventDocumentTemplate.getManagementEvent();

        SituationDocument situationDocument = SituationDocument.Factory.newInstance();
        SituationType situationType = situationDocument.addNewSituation();
        HeartbeatReportDocument heartbeatReportDocument = HeartbeatReportDocument.Factory
                .newInstance();
        heartbeatReportDocument.addNewHeartbeatReport().addNewReportSituation();

        XmlBeanUtils.addElementAsChild(situationType, heartbeatReportDocument);
        // situationType.setSuccessDisposition(SuccessDisposition.SUCCESSFUL);
        // situationType.setSituationTime(currentTime);
        // //Pririty 0-100
        // situationType.setPriority((short)50);
        // //0-6 Fatal,Critical,Major,Minor,Warning,Information,Unknown
        // situationType.setSeverity((short)1);

        XmlBeanUtils.addElementAsChild(eventType, situationDocument);
        
        //Initialize ManageMe message template
        manageMeRequestTemplate = ManageMeRequestDocument.Factory.newInstance();
        ManageMeRequest manageMeRequest = manageMeRequestTemplate.addNewManageMeRequest();
        ResourceDetails resourceDetail = manageMeRequest.addNewResourceDetails();
        resourceDetail.setManagmentAgentEpr(ownedResource.getResourceEndpoint());
        resourceDetail.setResourceName(ownedResource.getName());
        
        
        if(ownedResource.getName().getLocalPart().endsWith(".indiana.edu")){
            logHistoy = true;
        }
        String logHistoryStr = context.getBaseContext().getProperty(HasthiOptions.LOG_HISTORY);
        if(logHistoryStr != null){
            logHistoy = Boolean.parseBoolean(logHistoryStr);
        }
    }

    public void parentResourceInitialized() {
        for (Capability capability : ownedResource.getCapabilities()) {
            if (WSDMConstants.CAPABILITY_MATRICS1.equals(capability.getCapabilityDescription()
                    .getCapabilityURI())) {
                matrics1Capability = capability;
            }
        }
        changeBuffer = new ChangeBuffer();
        context.getChangeListener().addChangeListner(changeBuffer);
        logfile = LogFile.getLog("service"+Utils.getLocalHost());
        try{
            collector = new DataCollector("aoc("+ownedResource.getName().getLocalPart()+")",
                    logfile,DataCollector.WINDOW_SIZE);
        }catch(Throwable e){
        }
        
        //We schedule the Heartbeat thread 
        
        //timer = new Timer(true);
        int delay = 1000; // delay for 1 sec.
        int period = 1000 * super.context.getBaseContext().getMngAgentEpochSeconds();
        //timer.scheduleAtFixedRate(new HeartbeatTask(), delay, period);
        taskhandle = context.getScheduledExecutorService().scheduleAtFixedRate(new HeartbeatTask(), 
                delay, period, TimeUnit.MILLISECONDS);
    }

    public void sendHeartBeatEvent(PiggybackParameterListDocument piggybackParams) throws HasthiException {
        
        ManagementEventType managementEventType = eventDocumentTemplate.getManagementEvent();
        managementEventType.getReportTime().setTimeInMillis(System.currentTimeMillis());
        managementEventType.setPiggybackParameterList(piggybackParams.getPiggybackParameterList());
        //XmlBeanUtils.addElementAsChild(managementEventType, piggybackParams);
        
        //log.trace(XmlBeanUtils.prettyPrint(eventDocument));
        //TODO clean up measurements
        try {
            long point1 = System.nanoTime();
            sendEvent(eventDocumentTemplate);
            if(collector != null){
                collector.addAReading(System.nanoTime() - point1);    
            }
        } catch (Throwable e) {
            if(collector != null){
                collector.actionfailed();
            }
            log.warn("Failed to send heartbeats "+e.getMessage());
            ErrorManager.error(e);
        }
    }
    
    public void stop(){
        taskhandle.cancel(true);
        running = false;
        log.info("Shutting down heartbeat capability");
    }

    public class HeartbeatTask implements Runnable {
        
        public void run() {
            try {
                //System.out.print(JavaInstrumentation.getMemoryUsage() + " ");
                if(!running){
                    return;
                }
                
                PropertyType[] properties;
                PiggybackParameterListDocument document = PiggybackParameterListDocument.Factory.newInstance();
                XmlObject parameterListDocument = document.addNewPiggybackParameterList();
                
                if(logHistoy){
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("[History]:").append(ownedResource.getName()).append(":");
                    buffer.append(new Date()).append(":");
                    if (matrics1Capability != null) {
                        properties = matrics1Capability.getCapabilityDescription()
                                .getCapabilityConfig().getPropertyArray();
                        if (properties != null) {
                            for (PropertyType property : properties) {
                                Object value = matrics1Capability.getProperty(property
                                        .getName());
                                if(value != null){
                                    XmlBeanUtils.addNameValuePair(parameterListDocument, 
                                            property.getName(), value);
                                    buffer.append(property.getName().getLocalPart()).append("=").append(value).append(" ");
                                }else{
                                    throw new HasthiException("Capability "+ matrics1Capability.getCapabilityDescription().getCapabilityURI() 
                                            + " defines property "+ property.getName() + " but it does not have a value",FaultCode.ConfigError);
                                }
                            }
                        }
                    }
                    //Collect the Changes to the managment resource
                    for (ChangeEvent event : changeBuffer.reset()) {
                        XmlBeanUtils.addNameValuePair(parameterListDocument, 
                                event.getPropertyName(), event.getNewValue());
                        buffer.append(event.getPropertyName().getLocalPart()).append("=").append(event.getNewValue()).append(" ");
                    }
                    buffer.append("\n");
                    logfile.append(buffer.toString());
                }else{
                    //Collect the Matrics
                    if (matrics1Capability != null) {
//                        List<QName> matricsNames = ownedResource.getSystemHandle().getSupportedMatrics();
//                        if(matricsNames != null){
//                            for(QName matricName:matricsNames){
//                                Object value = ownedResource.getResourceProperty(matricName);
//                                XmlBeanUtils.addNameValuePair(parameterListDocument, 
//                                        matricName, value);
//                            }
//                        }
                        properties = matrics1Capability.getCapabilityDescription()
                                .getCapabilityConfig().getPropertyArray();
                        if (properties != null) {
                            for (PropertyType property : properties) {
                                Object value = matrics1Capability.getProperty(property
                                        .getName());
                                if(value != null){
                                    XmlBeanUtils.addNameValuePair(parameterListDocument, 
                                            property.getName(), value);
                                }else{
                                    throw new HasthiException("Capability "+ matrics1Capability.getCapabilityDescription().getCapabilityURI() 
                                            + " defines property "+ property.getName() + " but it does not have a value",FaultCode.ConfigError);
                                }
                            }
                        }
                    }
                    //Collect the Changes to the managment resource
                    for (ChangeEvent event : changeBuffer.reset()) {
                        XmlBeanUtils.addNameValuePair(parameterListDocument, 
                                event.getPropertyName(), event.getNewValue());
                    }
                }
                
                
                
                // If Management Endpoint is not defined, send a ManagmeMe
                // message to broker
                Object resourceProperty = ownedResource
                        .getResourceProperty(MANAGMENT_EVNET_SINK_PARAM);
                if (resourceProperty == null ||UNSPECIFIED_EPR.equals(resourceProperty)) {
                    //if(ownedResource.getLastManagmeTime() == -1 
                       //     || (System.currentTimeMillis() - ownedResource.getLastManagmeTime()) > 25000 ){
                        //We always send a same manage me message all the time
                        BaseContext baseContext = context.getBaseContext();
                        if(baseContext.isJoinViaBootstrap()){
                            String bootstrapsoapAddress = "http://"+baseContext.getBoostrapHost()+":"+baseContext.getBoostrapSoapPort();
                            log.debug(ownedResource.getName() + "-(ManagmeMe)-> "+bootstrapsoapAddress);
                            //System.out.println(ownedResource.getName() + "-(ManagmeMe)-> "+bootstrapsoapAddress);
                            try {
                                context.getUnicastContianier().send(new SimpleMessageContext(manageMeRequestTemplate,
                                        bootstrapsoapAddress,
                                        HasthiConstants.MANAGE_ME_ACTION));
                            } catch (Exception e) {
                                if(e.getMessage().contains("HTTP related exception: could not open connection to") ||
                                        e.getMessage().contains("java.net.ConnectException")){
                                    log.error(ownedResource.getName() + "-(ManagmeMe)-> "+bootstrapsoapAddress + " Failed"+e.getMessage());
                                }else{
                                    log.error(ownedResource.getName() + "-(ManagmeMe)-> "+bootstrapsoapAddress + " Failed",e);
                                    ErrorManager.error(e);
                                }
                            }
                        }else{
                            wseClient.publish(baseContext.getMessageBrokerUrl(),
                                    baseContext.getAdminTopic(), manageMeRequestTemplate.xmlText());
                        }
                        timeWithoutAManager++;
                        ownedResource.setLastManagmeTime(System.currentTimeMillis());
                        if(timeWithoutAManager > 5){
                            if(!FailureDetectionTools.isSocketUp(ownedResource.getResourceEndpoint())){
                                log.error(ownedResource.getName() + "socket "+ ownedResource.getResourceEndpoint() + 
                                        " is down, but managment agent is up, shutting down agent");
                                ownedResource.getSystemHandle().shutDown();
                                ownedResource.stop();
                            }
                            log.warn("No Manager Assgined for "+timeWithoutAManager + " timesteps");
                        }
                    //}
                }else{
                    timeWithoutAManager = 0;
                    sendHeartBeatEvent(document);    
                }
            } catch (Throwable e) {
                e.printStackTrace();
                log.error("Error within Heartbeat Thread ",e);
            }
        }
    }
    
    
    
    
}
